package com.tushare.util;

import com.tushare.entity.Daily;
import com.tushare.model.*;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class StockIndexUtil {
    private static final int SHORT_TIME = 12;
    private static final int LONG_TIME = 26;
    private static final int MID = 9;
    private static final int KDJ_TIME = 9; // 6，9，18 越小越灵敏，越大越准确

    public static List<StockIndex> cal(List<Daily> dailyList) {
        if (dailyList == null || dailyList.size() == 0) {
            return new ArrayList<>();
        }
        List<StockIndex> stockIndexList = initTrade(dailyList);
        initMaPrice(stockIndexList);
        initMaVol(stockIndexList);
        initMacd(stockIndexList);
        initKdj(stockIndexList);
        return stockIndexList;
    }

    /* 日线金叉并且底背离 */
    public static MacdRange calMacdDeviate(List<StockIndex> stockIndexList) {
        if (stockIndexList == null || stockIndexList.size() < 2) {
            return null;
        }
        StockIndex lastStockIndex = stockIndexList.get(stockIndexList.size() - 1);
        StockIndex preStockIndex = stockIndexList.get(stockIndexList.size() - 2);
        // macd金叉
        if (lastStockIndex.getMacd().getMacd() < 0 || preStockIndex.getMacd().getMacd() > 0) {
            return null;
        }
        List<MacdRange> macdRangeList = initMacdRange(stockIndexList);
        if (macdRangeList.size() < 4) {
            return null;
        }
        MacdRange lastRedMacdRange = macdRangeList.get(macdRangeList.size() - 1); // 红柱
        MacdRange lastGreenMacdRange = macdRangeList.get(macdRangeList.size() - 2); // 绿柱
        MacdRange preRedMacdRange = macdRangeList.get(macdRangeList.size() - 3); // 红柱
        MacdRange preGreenMacdRange = macdRangeList.get(macdRangeList.size() - 4); // 绿柱
        if (lastRedMacdRange.getDiffBegin() > preRedMacdRange.getDiffBegin() && lastGreenMacdRange.getDiffBegin() < 0) {
            if (lastGreenMacdRange.getMacdAbs() < preGreenMacdRange.getMacdAbs()
                    && lastGreenMacdRange.getCloseExtreme() > preGreenMacdRange.getCloseExtreme()) {
                lastRedMacdRange.setRemark("龙抬头且底背离");
            } else {
                lastRedMacdRange.setRemark("龙抬头");
            }
            return lastRedMacdRange;
        }
        return null;
    }

    /* JDK并且底背离 */
    public static KDJRange calKDJDeviate(List<StockIndex> stockIndexList) {
        if (stockIndexList == null || stockIndexList.size() < 3) {
            return null;
        }
        StockIndex lastStockIndex = stockIndexList.get(stockIndexList.size() - 1);
        // 剔除上部
        if (lastStockIndex.getKdj().getJ() >= 50) {
            return null;
        }
        List<KDJRange> kdjRangeList = initKDJRange(stockIndexList);
        if (kdjRangeList.size() < 4) {
            return null;
        }
        // 最近的第一个底部
        KDJRange lastNegativeKDJRange = kdjRangeList.get(kdjRangeList.size() - 1);
        // 最近的第二个底部
        KDJRange preNegativeKDJRange = kdjRangeList.get(kdjRangeList.size() - 3);
        if (lastNegativeKDJRange.getjAbs() > preNegativeKDJRange.getjAbs()
                && lastNegativeKDJRange.getCloseExtreme() < preNegativeKDJRange.getCloseExtreme()) {
            lastNegativeKDJRange.setDeviate_ratio(preNegativeKDJRange.getCloseExtreme() / lastNegativeKDJRange.getCloseExtreme() - 1);
            return lastNegativeKDJRange;
        }
        return null;
    }

    /* JDK超卖并且金叉 */
    public static KdjExtremeSelling calKDJExtremeSelling(List<StockIndex> stockIndexList) {
        if (stockIndexList == null || stockIndexList.size() == 0) {
            return null;
        }
        StockIndex lastStockIndex = stockIndexList.get(stockIndexList.size() - 1);
        Kdj kdj = lastStockIndex.getKdj();
        if (kdj.getJ() < 20 && kdj.getJ() > kdj.getD() && kdj.getJ() > kdj.getK()) {
            KdjExtremeSelling selling = new KdjExtremeSelling();
            selling.setTs_code(lastStockIndex.getTs_code());
            selling.setTrade_date(lastStockIndex.getTrade_date());
            return selling;
        }
        return null;
    }

    /* JDK没有新低，股价新低 */
    public static KDJRange calKDJPriceDeviate(List<StockIndex> stockIndexList) {
        if (stockIndexList == null || stockIndexList.size() == 0) {
            return null;
        }
        StockIndex lastStockIndex = stockIndexList.get(stockIndexList.size() - 1);
        if (lastStockIndex.getKdj().getJ() >= 50) {
            return null;
        }
        List<StockIndex> lastRangeStockIndexList = new ArrayList<>();
        for (int i = stockIndexList.size() - 1; i >= 0; i--) {
            StockIndex tempStockIndex = stockIndexList.get(i);
            if (tempStockIndex.getKdj().getJ() < 50) {
                lastRangeStockIndexList.add(tempStockIndex);
                continue;
            }
            break;
        }
        StockIndex kdjExtreme = lastRangeStockIndexList.get(0);
        for (StockIndex stockIndex : lastRangeStockIndexList) {
            if (stockIndex.getKdj().getJ() < kdjExtreme.getKdj().getJ()) {
                kdjExtreme = stockIndex;
            }
        }
        if (kdjExtreme.getTrade().getClose() > lastStockIndex.getTrade().getClose()) {
            KDJRange kdjRange = new KDJRange();
            kdjRange.setTs_code(lastStockIndex.getTs_code());
            kdjRange.setBegin_date(lastRangeStockIndexList.get(0).getTrade_date());
            kdjRange.setEnd_date(lastStockIndex.getTrade_date());
            kdjRange.setDeviate_ratio(kdjExtreme.getTrade().getClose() / lastStockIndex.getTrade().getClose() - 1);
            kdjRange.setjAbs(kdjExtreme.getKdj().getJ());
            kdjRange.setCloseExtreme(lastStockIndex.getTrade().getClose());
            return kdjRange;
        }

        return null;
    }

    private static void initKdj(List<StockIndex> stockIndexList) {
        List<Double> rsv = new ArrayList<>();
        int index = 0;
        int finalIndex = KDJ_TIME - 1;
        for (StockIndex stockIndex : stockIndexList) {
            if (index >= finalIndex) { // 8
                int start = index - finalIndex;
                double high9 = Float.MIN_VALUE;
                double low9 = Float.MAX_VALUE;
                while (start <= index) {
                    Trade tempTrade = stockIndexList.get(start).getTrade();
                    high9 = Math.max(tempTrade.getHigh(), high9);
                    low9 = Math.min(tempTrade.getLow(), low9);
                    start++;
                }
                rsv.add((stockIndex.getTrade().getClose() - low9) / (high9 - low9) * 100);
            } else {
                rsv.add(50.0);
            }
            index++;
        }
        StockIndex firstStockIndex = stockIndexList.get(0);
        Kdj firstKdj = firstStockIndex.getKdj();
        firstKdj.setK(50.0);
        firstKdj.setD(50.0);
        firstKdj.setJ(50.0);
        index = 1;
        for (StockIndex stockIndex : stockIndexList.subList(1, stockIndexList.size())) {
            Kdj kdj = stockIndex.getKdj();
            Kdj preKdj = stockIndexList.get(index - 1).getKdj();
            kdj.setK(2.0 / 3 * preKdj.getK() + 1.0 / 3 * rsv.get(index));
            kdj.setD(2.0 / 3 * preKdj.getD() + 1.0 / 3 * kdj.getK());
            kdj.setJ(3 * kdj.getK() - 2 * kdj.getD());
            index++;
        }
    }

    private static List<MacdRange> initMacdRange(List<StockIndex> stockIndexList) {
        List<MacdRange> macdRangeList = new ArrayList<>();
        MacdRange macdRange = new MacdRange();
        macdRangeList.add(macdRange);
        StockIndex firstStockIndex = stockIndexList.get(0);
        macdRange.setTs_code(firstStockIndex.getTs_code());
        macdRange.setBegin_date(firstStockIndex.getTrade_date());
        macdRange.setEnd_date(firstStockIndex.getTrade_date());
        macdRange.setDiffBegin(firstStockIndex.getMacd().getDiff());
        macdRange.setDeaBegin(firstStockIndex.getMacd().getDea());
        macdRange.setMacdAbs(firstStockIndex.getMacd().getMacd());
        macdRange.setCloseExtreme(firstStockIndex.getTrade().getClose());
        for (StockIndex stockIndex : stockIndexList.subList(1, stockIndexList.size())) {
            Macd macd = stockIndex.getMacd();
            if (macd.getMacd() * macdRange.getMacdAbs() >= 0) {
                macdRange.setEnd_date(stockIndex.getTrade_date());
                macdRange.setMacdAbs(Math.abs(macd.getMacd()) > Math.abs(macdRange.getMacdAbs())
                        ? macd.getMacd() : macdRange.getMacdAbs());
                macdRange.setCloseExtreme(macd.getMacd() < 0 ? Math.min(macdRange.getCloseExtreme(), stockIndex.getTrade().getClose())
                        : Math.max(macdRange.getCloseExtreme(), stockIndex.getTrade().getClose()));
            } else {
                macdRange = new MacdRange();
                macdRangeList.add(macdRange);
                macdRange.setTs_code(stockIndex.getTs_code());
                macdRange.setBegin_date(stockIndex.getTrade_date());
                macdRange.setEnd_date(stockIndex.getTrade_date());
                macdRange.setDiffBegin(stockIndex.getMacd().getDiff());
                macdRange.setDeaBegin(stockIndex.getMacd().getDea());
                macdRange.setMacdAbs(macd.getMacd());
                macdRange.setCloseExtreme(stockIndex.getTrade().getClose());
            }
        }
        return macdRangeList;
    }

    private static List<KDJRange> initKDJRange(List<StockIndex> stockIndexList) {
        List<KDJRange> kdjRangeList = new ArrayList<>();
        KDJRange kdjRange = new KDJRange();
        kdjRangeList.add(kdjRange);
        StockIndex firstStockIndex = stockIndexList.get(0);
        kdjRange.setTs_code(firstStockIndex.getTs_code());
        kdjRange.setBegin_date(firstStockIndex.getTrade_date());
        kdjRange.setEnd_date(firstStockIndex.getTrade_date());
        kdjRange.setjAbs(firstStockIndex.getKdj().getJ());
        kdjRange.setCloseExtreme(firstStockIndex.getTrade().getClose());
        for (StockIndex stockIndex : stockIndexList.subList(1, stockIndexList.size())) {
            Kdj kdj = stockIndex.getKdj();
            if ((kdj.getJ() - 50) * (kdjRange.getjAbs() - 50) >= 0) {
                kdjRange.setEnd_date(stockIndex.getTrade_date());
                kdjRange.setjAbs(Math.abs(kdj.getJ() - 50) > Math.abs(kdjRange.getjAbs() - 50)
                        ? kdj.getJ() : kdjRange.getjAbs());
                kdjRange.setCloseExtreme((kdj.getJ() - 50) < 0 ? Math.min(kdjRange.getCloseExtreme(), stockIndex.getTrade().getClose())
                        : Math.max(kdjRange.getCloseExtreme(), stockIndex.getTrade().getClose()));
            } else {
                kdjRange = new KDJRange();
                kdjRangeList.add(kdjRange);
                kdjRange.setTs_code(stockIndex.getTs_code());
                kdjRange.setBegin_date(stockIndex.getTrade_date());
                kdjRange.setEnd_date(stockIndex.getTrade_date());
                kdjRange.setjAbs(kdj.getJ());
                kdjRange.setCloseExtreme(stockIndex.getTrade().getClose());
            }
        }
        return kdjRangeList;
    }

    private static void initMacd(List<StockIndex> stockIndexList) {
        float shortA = 2.0f / (SHORT_TIME + 1);
        float longA = 2.0f / (LONG_TIME + 1);
        float midA = 2.0f / (MID + 1);
        StockIndex preStockIndex = stockIndexList.get(0);
        Macd macd = preStockIndex.getMacd();
        Trade trade = preStockIndex.getTrade();
        macd.setEmaShort(trade.getClose());
        macd.setEmaLong(trade.getClose());
        macd.setDiff(0);
        macd.setDea(0);
        for (StockIndex stockIndex : stockIndexList.subList(1, stockIndexList.size())) {
            macd = stockIndex.getMacd();
            trade = stockIndex.getTrade();
            macd.setEmaShort(shortA * trade.getClose() + (1 - shortA) * preStockIndex.getMacd().getEmaShort());
            macd.setEmaLong(longA * trade.getClose() + (1 - longA) * preStockIndex.getMacd().getEmaLong());
            macd.setDiff(macd.getEmaShort() - macd.getEmaLong());
            macd.setDea(midA * macd.getDiff() + (1 - midA) * preStockIndex.getMacd().getDea());
            macd.setMacd(macd.getDiff() * 2 - macd.getDea() * 2);
            preStockIndex = stockIndex;
        }
    }

    private static void initMaVol(List<StockIndex> stockIndexList) {
        List<Float> volList = stockIndexList.stream().map(stockIndex -> stockIndex.getTrade().getVol()).collect(Collectors.toList());
        int index = 0;
        for (StockIndex stockIndex : stockIndexList) {
            Ma maPrice = stockIndex.getMaVol();
            maPrice.setThree(calMa(volList, index, 3));
            maPrice.setFive(calMa(volList, index, 5));
            maPrice.setTen(calMa(volList, index, 10));
            maPrice.setMonth(calMa(volList, index, 30));
            index++;
        }
    }

    private static void initMaPrice(List<StockIndex> stockIndexList) {
        List<Float> closeList = stockIndexList.stream().map(stockIndex -> stockIndex.getTrade().getClose()).collect(Collectors.toList());
        int index = 0;
        for (StockIndex stockIndex : stockIndexList) {
            Ma maPrice = stockIndex.getMaPrice();
            maPrice.setThree(calMa(closeList, index, 3));
            maPrice.setFive(calMa(closeList, index, 5));
            maPrice.setTen(calMa(closeList, index, 10));
            maPrice.setMonth(calMa(closeList, index, 30));
            index++;
        }
    }

    private static float calMa(List<Float> closeList, int index, int size) {
        float totalClose = 0.0f;
        List<Float> subClose = closeList.subList(index > size ? index - size : 0, index + 1);
        for (Float close : subClose) {
            totalClose += close;
        }
        return totalClose / subClose.size();
    }

    private static List<StockIndex> initTrade(List<Daily> dailyList) {
        List<StockIndex> stockIndexList = new ArrayList<>();
        dailyList.forEach(daily -> {
            StockIndex stockIndex = new StockIndex();
            stockIndex.setTs_code(daily.getTs_code());
            stockIndex.setTrade_date(daily.getTrade_date());
            Trade trade = new Trade();
            trade.setOpen(daily.getOpen());
            trade.setHigh(daily.getHigh());
            trade.setLow(daily.getLow());
            trade.setClose(daily.getClose());
            trade.setVol(daily.getVol());
            stockIndex.setTrade(trade);
            stockIndex.setMaPrice(new Ma());
            stockIndex.setMaVol(new Ma());
            stockIndex.setKdj(new Kdj());
            stockIndex.setMacd(new Macd());
            stockIndexList.add(stockIndex);
        });
        return stockIndexList;
    }
}
